package org.arl.modem.transportARQ;

import java.util.LinkedList;
import java.util.List;

import org.arl.modem.codec.CodecPacket;
import org.arl.modem.codec.Encoder;
import org.arl.modem.codec.LTEncoder;
import org.arl.modem.codec.LTKentMapEncoder;
import org.arl.modem.codec.RLFountainEncoder;

public class PacketFactory {
   private int totalPackets;
   private int packetSize;
   private int blockSize; // m in paper
   private int lastPacketId;
   private List<Packet> packets;
   private byte[] padData;
   private int currentBlockID = 0;
   private List<Block> blocks;
   private TransportARQ tarq;
   private Encoder encode;

   public PacketFactory(int packetSize, int blockSize, TransportARQ tarq) {
      this.packetSize = packetSize;
      this.blockSize = blockSize;
      this.tarq = tarq;
      blocks = new LinkedList<Block>();
      tarq.mylog.fine("size of packet in factory:" + packetSize);

   }

   public int getBlockSize() {
      return blockSize;
   }

   public void setBlockSize(int blockSize) {
      this.blockSize = blockSize;
   }

   /**
    * converts the data into packets and assign the packetId and status
    * 
    * @param buf
    * @return total number of packets in the buffer
    */
   public int createPackets(byte[] buf) {
      if (TransportARQ.ARQ_TYPE == 3) {
         System.out.println("Encoder parameters:2:" + packetSize + ":100:5.0:"
               + buf.length);
         switch (TransportARQ.RATELESS_CODING_TYPE) {
         case 0:
            encode = new LTKentMapEncoder(2, packetSize, 100, (float) 5,
                  (float) 0.01, (float) 0.5, buf.clone());
            break;
         case 1:
            encode = new LTEncoder(2, packetSize, 100, (float) 0.01, (float) 5,
                  buf.clone());
            break;
         case 2:
            encode = new RLFountainEncoder(2, packetSize, 100, buf.clone());
            break;
         default:
            break;
         }

         totalPackets = encode.readMessage();
      } else {
         int padPacketData = 0;
         if (buf.length % packetSize != 0) {
            padPacketData = packetSize - buf.length % packetSize;
         }
         byte[] padBytes = new byte[padPacketData];
         for (int y = 0; y < padPacketData; y++) {
            padBytes[y] = -1;
         }
         byte[] bufNew = new byte[buf.length + padBytes.length];

         for (int i = 0; i < bufNew.length; ++i) {
            bufNew[i] = i < buf.length ? buf[i] : padBytes[i - buf.length];
         }
         totalPackets = bufNew.length / packetSize;

         byte[][] data = new byte[bufNew.length / packetSize][packetSize];
         padData = new byte[packetSize];
         packets = new LinkedList<Packet>();
         for (int i = 0; i < totalPackets; i++) {
            // Encoder parameters:2:66:100:5.0:2000

            for (int k = 0; k < packetSize; k++) {
               data[i][k] = bufNew[i * packetSize + k];
            }
            Packet p = new Packet(i, packetSize, data[i]);
            packets.add(p);
            lastPacketId = i;
         }
         tarq.mylog.fine("tsp:Factory total packets created:" + totalPackets);
      }
      return totalPackets;
   }

   /**
    * Get the next block of packets
    */
   public Block getNextBlock() {
      List<Packet> pkts = new LinkedList<Packet>();
      switch (TransportARQ.ARQ_TYPE) {
      case 0:// fixed ARQ

         break;
      case 1: // fixed JARQ

         break;
      case 2:// variable ARQ
         tarq.lastblockSize = TransportARQ.blockSize;
         if (packets.size() < blockSize) {
            TransportARQ.blockSize = packets.size();
            tarq.lastblockSize = TransportARQ.blockSize;
            if (packets.size() == 0) {
               TransportARQ.blockSize = 2;
               this.blockSize = 2;
            } else
               this.blockSize = packets.size();
         }
         tarq.mylog.fine("                    BLOCK SIZE:::" + this.blockSize);
         break;
      case 3:// rate less JARQ
         for (int i = 0; i < this.blockSize; i++) {
            // System.out.println("start");
            CodecPacket cp = null;
            try {
               cp = encode.encodeMessage();
            } catch (Exception e) {
               e.printStackTrace();
            }
            // System.out.println("end");
            pkts.add(new Packet(9, packetSize, cp.packet.clone(), cp.header,
                  cp.degree));
            // System.out.println("sed:degree:" + cp.header + ":" + cp.degree);
         }
         break;
      }
      boolean flag = false;
      if (TransportARQ.ARQ_TYPE != 3) {
         System.out.println("block size!!!" + blockSize);
         System.out.println("PACKETS SIZE in FACTORY before=" + packets.size());
         tarq.mylog.fine("PACKETS SIZE in FACTORY before=" + packets.size());
         for (int i = 0; i < this.blockSize; i++) {

            if (!packets.isEmpty()) {
               flag = true;
               Packet p = packets.get(0);
               packets.remove(p);
               if (!pkts.add(p)) {
                  lastPacketId++;
                  pkts.add(new Packet(lastPacketId, packetSize, padData));
               } else {
                  tarq.mylog.fine("Error adding packet id:" + p.getPacketId());
               }
            } else {
               pkts.add(new Packet(-1, packetSize, padData));

            }
         }
         System.out.println("PACKETS SIZE in FACTORY after=" + packets.size());
         tarq.mylog.fine("PACKETS SIZE in FACTORY after=" + packets.size());
      }
      Block b = null;
      if (!flag && !blocks.isEmpty() && TransportARQ.ARQ_TYPE != 3) {
         b = blocks.remove(0);
      } else {
         b = new Block(currentBlockID, pkts);
      }
      System.out.println("numder of packets in block:" + pkts.size());

      blocks.add(b);
      currentBlockID++;
      tarq.mylog.fine("tsp: Getting block no:" + b.getBlockID());

      return b.clone();
   }

   /**
    * ACK the specified packet
    */
   public void ackPacket(int blockId, boolean[] bo) {
      java.util.ArrayList<Packet> remPktIdx = new java.util.ArrayList<Packet>();
      System.out.println("acking block id:" + blockId + "  packetid");
      for (boolean bb : bo) {
         System.out.print(bb + " ");
      }
      System.out.println();
      int totalbo = bo.length;
      List<Packet> allPkts = null;
      for (Block b : blocks) {
         // System.out.println("block id:" + b.getBlockID());
         if (b.getBlockID() == blockId) {
            allPkts = b.getPackets();
            // System.out.println("selected block has pkts:" + allPkts.size());
            for (Packet p : allPkts) {
               // System.out.println("pkt details:" + p.getBlockPacketId() + ":"
               // + p.getPacketId());
               // if (bo[totalbo - p.getBlockPacketId() - 1]) {
               if (bo[p.getBlockPacketId() - 1]) {
                  // System.out.println("rmoving pkt ID:" + p.getPacketId());

                  remPktIdx.add(p);
               }
            }
            allPkts.removeAll(remPktIdx);
            break;
         }
      }

   }

   // /**
   // * ACK the specified packet
   // */
   // public void ackPacket(int blockId, byte[] buf) {
   // java.util.ArrayList<Packet> remPktIdx = new java.util.ArrayList<Packet>();
   // // System.out.println("acking block id:" + blockId + "  packetid");
   // for (boolean bb : bo) {
   // System.out.print(bb + " ");
   // }
   // System.out.println();
   // int totalbo = bo.length;
   // List<Packet> allPkts = null;
   // for (Block b : blocks) {
   // System.out.println("block id:" + b.getBlockID());
   // if (b.getBlockID() == blockId) {
   // allPkts = b.getPackets();
   // System.out.println("selected block has pkts:" + allPkts.size());
   // for (Packet p : allPkts) {
   // System.out.println("pkt details:" + p.getBlockPacketId() + ":" +
   // p.getPacketId());
   // // if (bo[totalbo - p.getBlockPacketId() - 1]) {
   // if (bo[p.getBlockPacketId() - 1]) {
   // System.out.println("rmoving pkt ID:" + p.getPacketId());
   //
   // remPktIdx.add(p);
   // }
   // }
   // allPkts.removeAll(remPktIdx);
   // break;
   // }
   // }
   //
   // }
   /**
    * move the unAcked packets back to the factory and removes this block from
    * list
    * @param blockId
    */
   public void cleanupBlock(int blockId) {
      // tarq.mylog.fine("cleaning up theblock:" + blockId);
      for (Block b : blocks) {
         if (b.getBlockID() == blockId) {
            for (Packet p : b.getPackets()) {
               p.setBlockPacketId(0);
               packets.add(p);
            }
            break;
         }

      }
      for (Block b : blocks) {
         if (b.getBlockID() == blockId) {
            if (blocks.remove(b)) {
               // tarq.mylog.fine("tsp:removing block");
            } else {
               tarq.mylog.fine("tsp:error removing block");
            }
            break;
         }
      }
      System.out.println("factory size:" + this.packets.size());
      tarq.mylog.fine("tsp:Remaining packets in factory:" + packets.size());
   }

   public int getBlockIdtobeAcked(int K) {

      int ackedblockid = -1;
      long mingap = 100000000;
      for (TxHelper h : tarq.txhelper) {
         long temp = h.getExpectedACKTimestamp()
               - (tarq.currentRxTime / 1000 - h.getLastPktTxTimestamp());
         // System.out.println("temp for=" + h.getBlockId() + "  is=" + temp);
         if (temp < 0)
            temp = temp * -1;
         if (temp < mingap) {
            mingap = temp;
            ackedblockid = h.getBlockId();
         }

      }
      // System.out.println("ack block id:**********" + ackedblockid);
      return ackedblockid;
      // int b = blocks.size() - K - 1;
      // if (b < 0)
      // b = 0;
      // if (blocks.isEmpty())
      // return 0;
      // int blockID = blocks.get(b).getBlockID();
      // System.out.println("value of b=" + b);
      // System.out.println("block id for o index=" + blockID);
      // System.out.println("block id for 1 index=" +
      // blocks.get(1).getBlockID());
      // return blockID;
   }
}
